﻿#include <iostream>
#include <string>
#include <cmath>
//#include <filesystem>
#include <fstream>
#include <vector>
#include <optional>
#include <sstream>
#include <boost/filesystem.hpp>
#include "Tools.h"

using namespace std;
//namespace fs = std::filesystem;
namespace fs = boost::filesystem;

constexpr float epsision = 0.000001f;

struct FbxInfo
{
    std::string filepath{ "" };
    bool bBinary{ true };

    char type{ '\0' };
    double value{ 0.0 };
    long long pos{ -1 };

    int line{ -1 };

    FbxInfo(std::string fp, char t, double v, long long p) :
        filepath(fp), type(t), value(v), pos(p) {}
    FbxInfo() = default;
};

bool IsBinaryFbx(const std::vector<char>& buffer)
{
    const char* str = "Kaydara FBX Binary";
    size_t len = strlen(str);
    return strncmp(&buffer[0], str, len) == 0;
}

std::optional<FbxInfo> ProcessAsciiFbx(const std::string& filepath, const std::vector<char>& buffer, bool& ok)
{
    FbxInfo info;
    info.filepath = filepath;
    info.bBinary = false;
    info.line = 0;

    std::istringstream stream(&buffer[0]);
    std::string lineStr;
    while (!stream.eof())
    {
        info.line++;
        std::getline(stream, lineStr);

        if (lineStr.find("\"UnitScaleFactor\"") != std::string::npos)
        {
            auto idx = std::max(lineStr.find_first_not_of('\t'), lineStr.find_first_not_of(' '));
            std::cout << lineStr.substr(idx) << std::endl;

            auto lstStr = Tools::Split(lineStr, ':');
            if (lstStr.size() != 2)
            {
                return {};
            }

            lstStr = Tools::Split(lstStr[1], ',');
            if (lstStr.size() != 5)
            {
                return {};
            }

            auto GetStr = [](const std::string& s)->std::string {
                size_t idx0 = s.find_first_of("\"");
                if (idx0 == std::string::npos) return "";
                
                size_t idx1 = s.find_last_of("\"");
                if (idx0 == std::string::npos) return "";
                
                std::string str = s.substr(idx0 + 1, idx1 - idx0 - 1);
                return str;
            };

            auto str = GetStr(lstStr[1]);
            info.type = (str == "double") ? 'D' : 'F';
            std::cout << "type1: " << str << std::endl;

            std::cout << "type1: " << GetStr(lstStr[2]) << std::endl;
            std::cout << "flags: " << GetStr(lstStr[3]) << std::endl;

            float v = std::atof(lstStr[4].c_str());
            std::cout << "UnitScaleFactor: " << v << std::endl;
            if (std::fabs(v - 100.0) < epsision)
            {
                return {};
            }

            info.value = v;
            return info;
        }
    }

    ok = false;
    return {};
}

std::optional<FbxInfo> ProcessBinaryFbx(const std::string& filepath, const std::vector<char>& buffer, bool& ok)
{
    char constBuf[15] = { 'U', 'n', 'i', 't', 'S', 'c', 'a', 'l', 'e', 'F', 'a', 'c', 't', 'o', 'r' };
    bool find = false;

    size_t pos = 0;
    for (; pos < buffer.size() - sizeof(constBuf); pos++)
    {
        if (memcmp(&buffer[pos], constBuf, sizeof(constBuf)) == 0)
        {
            find = true;
            break;
        }
    }

    if (!find)
    {
        std::cout << "can not find the sign string.\n";
        ok = false;
        return {};
    }

    pos += sizeof(constBuf);

    char type;
    std::uint32_t len = 0;
    std::string str;
    {
        type = buffer[pos];
        if (type != 'S')
        {
            std::cout << "[1] type1 error: " << type << std::endl;
            ok = false;
            return {};
        }
        pos += 1;

        memcpy(&len, &buffer[pos], sizeof(std::uint32_t));
        pos += sizeof(std::uint32_t);

        str.resize(len);
        memcpy(&str[0], &buffer[pos], len);
        pos += len;
        std::cout << "type1: " << str << std::endl;
    }

    {
        type = buffer[pos];
        if (type != 'S')
        {
            std::cout << "[2] type2 error: " << type << std::endl;
            ok = false;
            return {};
        }
        pos += 1;

        memcpy(&len, &buffer[pos], sizeof(std::uint32_t));
        pos += sizeof(std::uint32_t);

        str.resize(len);
        memcpy(&str[0], &buffer[pos], len);
        pos += len;
        std::cout << "type2: " << str << std::endl;
    }

    {
        type = buffer[pos];
        if (type != 'S')
        {
            std::cout << "[2] type2 error: " << type << std::endl;
            ok = false;
            return {};
        }
        pos += 1;

        memcpy(&len, &buffer[pos], sizeof(std::uint32_t));
        pos += sizeof(std::uint32_t);

        str.resize(len);
        memcpy(&str[0], &buffer[pos], len);
        pos += len;
        std::cout << "flags: " << str << std::endl;
    }

    {
        type = buffer[pos];
        pos += 1;

        if (type == 'D')
        {
            double v = 0.0;
            memcpy(&v, &buffer[pos], sizeof(double));
            std::cout << "UnitScaleFactor: " << v << std::endl;

            if (std::fabs(v - 100.0) < epsision)
                return {};

            std::cout << "pos: " << pos << std::endl;
            return FbxInfo(filepath, type, v, pos);
        }
        else if (type == 'F')
        {
            float v = 0.0;
            memcpy(&v, &buffer[pos], sizeof(float));
            std::cout << "UnitScaleFactor: " << v << std::endl;

            if (std::fabs(v - 100.0) < epsision)
                return {};

            std::cout << "pos: " << pos << std::endl;
            return FbxInfo(filepath, type, v, pos);
        }
        else
        {
            ok = false;
            return {};
        }
    }
}

std::optional<FbxInfo> StatisticsFbxFiles(const std::string& filepath, bool& ok)
{
    ok = true;

    std::vector<char> buffer = Tools::ReadFile(filepath);
    if (!buffer.empty())
    {
        bool bBinary = IsBinaryFbx(buffer);
        if (!bBinary)
        {
            std::cout << "format: ascii\n";
            return ProcessAsciiFbx(filepath, buffer, ok);
        }
        else
        {
            std::cout << "format: binary\n";
            return ProcessBinaryFbx(filepath, buffer, ok);
        }
    }
    else
    {
        ok = false;
    }

    return {};
}

bool FixBinaryFbxUnitFactor(const FbxInfo& fi)
{
    std::vector<char> buffer = Tools::ReadFile(fi.filepath);
    if (buffer.empty()) return false;

    if (fi.type == 'D')
    {
        double dv = 100.0;
        memcpy(&buffer[fi.pos], (const char*)&dv, sizeof(double));
    }
    else if (fi.type == 'F')
    {
        float fv = 100.0;
        memcpy(&buffer[fi.pos], (const char*)&fv, sizeof(float));
    }

    std::ofstream ofile(fi.filepath, std::ios::binary | std::ios::out);
    if (ofile.is_open())
    {
        ofile.write(&buffer[0], buffer.size());
        ofile.close();
        return true;
    }

    return false;
}

bool FixAsciiFbxUnitFactor(const FbxInfo& fi)
{
    std::vector<char> buffer = Tools::ReadFile(fi.filepath);
    if (buffer.empty()) return false;
    
    std::ostringstream os;
    std::istringstream is(&buffer[0]);
    std::string lineStr;
    size_t l = 0;
    while (!is.eof())
    {
        l++;
        std::getline(is, lineStr);
        if (l == fi.line)
        {
            size_t idx = lineStr.find_last_of(",");
            lineStr = lineStr.substr(0, idx + 1) + "100";
        }
        os << lineStr << '\n';
    }

    if (os.str().empty()) return false;

    std::ofstream ofile(fi.filepath, std::ios::out | std::ios::binary);
    if (ofile.is_open())
    {
        ofile.write(os.str().c_str(), os.str().size());
        ofile.close();
        return true;
    }

    return false;
}

int main(int argc, char** argv)
{
    if (argc < 2) return -1;

    std::string srcDir = argv[1];
    if (!fs::exists(srcDir))
    {
        std::cout << "can not find directory: " << srcDir << std::endl;
        return -1;
    }

    std::string bakDir = "";
    if (argc > 2)
    {
        bakDir = argv[2];
        if (!fs::exists(bakDir))
        {
            fs::create_directories(bakDir);
        }
    }

    cout << "statistics fbx----------------------------------------" << endl;
    std::vector<FbxInfo> fbxInfos;
    std::vector<std::string> errorFiles;
    size_t num = 1;

    for (auto entry : fs::recursive_directory_iterator(srcDir))
    {
        if (!fs::is_regular_file(entry.path())) continue;

        std::string filepath = entry.path().generic_string();
        if (filepath.size() < 4) continue;

        std::string ext = filepath.substr(filepath.size()-3);
        if (ext != "fbx" && ext != "FBX") continue;

        std::cout << num++ << "\t" << filepath << std::endl;

        bool ok = true;
        auto optFbxInfo = StatisticsFbxFiles(filepath, ok);
        if (optFbxInfo.has_value())
        {
            fbxInfos.push_back(optFbxInfo.value());
        }
        else
        {
            if (!ok)
                errorFiles.push_back(filepath);
        }
    }

    if (!fbxInfos.empty())
    {
        std::cout << "\n\n\nlist fbx--------------------------------------------------------------------\n";

        int i = 1;
        for (const auto& fi : fbxInfos)
        {
            std::cout << i++ << "\t";
            std::cout << fi.filepath << "\t";
            if (fi.bBinary)
            {
                std::cout << "binary\ttype: " << fi.type << "\tvalue: " << fi.value << "\tpos: " << fi.pos << "\n";
            }
            else
            {
                std::cout << "ascii\ttype: " << fi.type << "\tvalue: " << fi.value << "\tline: " << fi.line << "\n";
            }
        }
    }

    if (!errorFiles.empty())
    {
        std::cout << "\n\n\nlist error fbx--------------------------------------------------------------------\n";

        int i = 1;
        for (const auto& filepath : errorFiles)
        {
            std::cout << i++ << "\t" << filepath << "\n";
        }
    }

    if (!bakDir.empty() && !fbxInfos.empty())
    {
        std::cout << "\n\n\nmodify fbx--------------------------------------------------------------------\n";
        int succ = 0;
        int failed = 0;

        int i = 1;
        for (const auto& fi : fbxInfos)
        {
            std::string dstFilePath = (fs::path(bakDir) / fs::path(fi.filepath).filename().string()).generic_string();
            if (!fs::exists(dstFilePath))
            {
                fs::copy(fi.filepath, dstFilePath);
            }

            std::cout << i++ << "/" << fbxInfos.size() << "\t";
            bool ret = fi.bBinary ? FixBinaryFbxUnitFactor(fi) : FixAsciiFbxUnitFactor(fi);
            if (ret)
            {
                succ++;
                std::cout << "successfully\t" << fi.filepath << std::endl;
            }
            else
            {
                failed++;
                std::cout << "failed\t" << fi.filepath << std::endl;
            }
        }

        std::cout << "succ: " << succ << "\tfailed: " << failed << std::endl;
    }

    return 0;
}
